////////////////////////////////////////////////////////////////////////////
//
//  Crytek Engine Source File.
//  Copyright (C), Crytek Studios, 2002.
// -------------------------------------------------------------------------
//  File name:   Particle.cpp
//  Created:     28/5/2001 by Vladimir Kajalin
//  Modified:    11/3/2005 by Scott Peter
//  Compilers:   Visual Studio.NET
// -------------------------------------------------------------------------
//  History:
//
////////////////////////////////////////////////////////////////////////////

#include "StdAfx.h"

#include "Particle.h"
#include "ParticleEmitter.h"
#include "ParticleContainer.h"
#include "terrain.h"
#include "terrain_water.h"
#include "GeomQuery.h"
#include "MatMan.h"

#include <SpuUtils.h>

#define fMAX_NONUNIFORM_TRAVEL	0.25f
#define fCOLLIDE_BUFFER_DIST		0.001f
#define fATTACH_BUFFER_DIST			0.01f
#define fMIN_TEST_AHEAD_MULT		2.5f

SPU_NO_INLINE void RotateTo( Quat& qRot, Vec3 const& vNorm )
{
	qRot = Quat::CreateRotationV0V1( qRot.GetColumn1(), vNorm ) * qRot;
}

// A measure of how far a midpoint is from the line.
// Return square of double triangle area.
SPU_NO_INLINE float InterpVariance(Vec3 const& v0, Vec3 const& v1, Vec3 const& v2)
{
	Vec3 v02 = v2 - v0;
	Vec3 v01 = v1 - v0;
	float fT = max(div_min(v01*v02, v02.len2(), 1.f), 0.f);
	return (v02 * fT - v01).len2();
}

SPU_NO_INLINE float InterpVariance(QuatTS const& qt0, QuatTS const& qt1, QuatTS const& qt2)
{
	return InterpVariance(qt0.t, qt1.t, qt2.t)
		+ InterpVariance(qt0.q.GetColumn2() * qt0.s, qt1.q.GetColumn2() * qt1.s, qt2.q.GetColumn2() * qt2.s);
}

SPU_NO_INLINE float CParticle::TravelSlide( SParticleMoveState& state, SSlideInfo& sliding, float fTime, const Vec3& vExtAccel, float fMaxSlide, float fMinStepTime ) const
{
	float fTravelTime = 0.f;
	while (fTime > 0.f)
	{
		// Convert wind drag to acceleration.
		float fPenAccel = vExtAccel * sliding.vNormal;
		if (fPenAccel >= 0.f)
		{
			// Not sliding along plane.
			sliding.ClearSliding(sliding.vNormal);
			break;
		}

		GetContainer().GetCounts().ParticlesReiterate += 1.f;

		// There is a force against the sliding plane.
		// Sliding if velocity is currently into the plane, or max bounce distance is less than the border.
		Vec3 vAccel = vExtAccel - sliding.vNormal * fPenAccel;
		float fPenVel = state.m_Vel.vLin * sliding.vNormal;
		state.m_Vel.vLin -= sliding.vNormal * fPenVel;
		float fFrictionAccel = sliding.fFriction * fPenAccel;
		float fVV = state.m_Vel.vLin.GetLengthSquared();
		if (fVV < FLT_EPSILON)
		{
			// Reduce acceleration by friction.
			float fAA = vAccel.GetLengthSquared();
			if (fAA <= sqr(fFrictionAccel))
			{
				// No acceleration to overcome friction.
				fTravelTime += fTime;
				break;
			}
			vAccel *= 1.f + fFrictionAccel * isqrt_tpl(fAA);
		}
		else
			// Reduce acceleration in velocity direction.
			vAccel += state.m_Vel.vLin * (isqrt_tpl(fVV) * fFrictionAccel);

		// Clamp based on estimated slide deviation.
		float fStepTime = fTime;
		float fMaxSlideTime = sqrt_fast_tpl(-2.f * fMaxSlide / fPenAccel);
		float fMaxTime = max(fMaxSlideTime - sliding.fSlidingTime, fMinStepTime);
		if (fMaxTime > 1e-6f)
		{
			fStepTime = min(fStepTime, fMaxTime);

			// Detect slide stoppage, and limit travel time.
			float fVA = state.m_Vel.vLin * vAccel;
			if (fVV < -fVA * fStepTime)
			{
				// Stopped.
				fStepTime = -fVV / fVA;
				state.m_Loc.t += state.m_Vel.vLin * fStepTime + vAccel * (fStepTime*fStepTime*0.5f);
				state.m_Vel.vLin.zero();
				state.m_Vel.vRot.zero();
				fTravelTime += fTime;
				fTime = 0.f;
			}
			else
			{
				state.m_Loc.t += state.m_Vel.vLin * fStepTime - vAccel * (fStepTime*fStepTime*0.5f);
				state.m_Vel.vLin += vAccel * fStepTime;
				state.m_Vel.vRot *= fStepTime * div_min(-fVA, fVV, 1.f);
				fTravelTime += fStepTime;
				fTime -= fStepTime;
			}

			sliding.fSlidingTime += fStepTime;
		}

		if (sliding.fSlidingTime >= fMaxSlideTime - 1e-6f)
		{
			// Require retest against entity.
			ray_hit hit;
			Vec3 vTest = vExtAccel.GetNormalized() * fMaxSlide;
			GetContainer().GetCounts().ParticlesClip += 1.f;
			if (!SPhysEnviron::PhysicsCollision( hit, state.m_Loc.t - vTest, state.m_Loc.t + vTest, 
					fCOLLIDE_BUFFER_DIST, sliding.pEntity ? ENV_COLLIDE_PHYSICS : ENV_TERRAIN, sliding.pEntity ))
			{
				sliding.Clear();
				break;
			}

			float fPenPos = (state.m_Loc.t - hit.pt) * hit.n;
			if (fPenPos < 0.f)
				state.m_Loc.t -= fPenPos * hit.n;
			sliding.vNormal = hit.n;
			sliding.fSlidingTime = 0.f;
			float fBounce_;
			GetCollisionParams(hit.surface_idx, fBounce_, sliding.fFriction);
		}
	}

	return fTravelTime;
}

SPU_NO_INLINE void CParticle::Move( SParticleMoveState& state, float fTime, SExternForces const& forces_ext, ParticleTarget const& target ) const
{
	GetContainer().GetCounts().ParticlesReiterate += 1.f;

	ResourceParticleParams const& params = GetParams();

	float fRelativeAge = GetRelativeAge(fTime*0.5f);

	SLocalForces forces;
	forces.vAccel = params.vAcceleration + forces_ext.vAccel * params.fGravityScale.GetValueFromMod(m_BaseMods.GravityScale, fRelativeAge);
	forces.vWind = forces_ext.vWind;
	forces.fDrag = params.fAirResistance.GetValueFromMod(m_BaseMods.AirResistance, fRelativeAge); 

	Vec3 vVelOrig = state.m_Vel.vLin;
	
	if (params.fTurbulence3DSpeed)
	{
		/*
			Random acceleration A (wind velocity times air resistance) applied continuously.
			In time quantum dt, dV = A dt, and dP = A/2 dt^2. 
			In larger time periods, avg dv(t) = a dt sqrt(t/dt), and avg dp(t) = a/2 dt sqrt(t/dt) t.
			So effective acceleration ae(t) = dv(t) / t = a dt sqrt(t/dt) / t = a sqrt(dt/t)

			Turbulence3DSpeed parameter indicates average velocity change dV in 1 s = dt,
			therefore, the param is used as the acceleration base, and ae(t) = A / sqrt(t)
		*/

		float fTurb = params.fTurbulence3DSpeed.GetValueFromMod(m_BaseMods.Turbulence3DSpeed, fRelativeAge);
		fTurb *= isqrt_tpl(fTime);

		Vec3 vTurbulence;
		for (int a = 0; a < 3; a++)
		{
			CChaosKey key(*(uint32*)&state.m_Loc.t[a]);
			vTurbulence[a] = (key.Jumble(this) * 2.f - 1.f) * fTurb;
		}
		forces.vAccel += vTurbulence;
	}

	// 2D spiral turbulence.
	if (params.fTurbulenceSize * params.fTurbulenceSpeed != 0.f)
	{
		// Apply exact rotation differences between this and next step.
		Vec3 vRotate = VortexRotation(state);

		Travel( state.m_Loc.t, state.m_Vel.vLin, fTime, forces );

		state.m_Loc.t += VortexRotation(state, fTime) - vRotate;
		// Do not adjust velocity, this is an artificial spiral motion.
	}
	else
	{
		Travel( state.m_Loc.t, state.m_Vel.vLin, fTime, forces );
	}

	// Compute targeting motion.
	if (target.bTarget)
	{
		state.m_Vel.vLin = (state.m_Vel.vLin + vVelOrig) * 0.5f;
		TargetMovement( state, fTime, fRelativeAge, target );
	}

	if (forces.fDrag * params.fRotationalDragScale > 0.f)
	{
		state.m_Vel.vRot *= expf(-forces.fDrag * params.fRotationalDragScale * fTime);
		if (state.m_Vel.vRot.GetLengthSquared() == 0.f)
			state.m_Vel.vRot.zero();
	}

	if (!params.bSpaceLoop)
		DebugBounds(state);
}

void CParticle::DebugBounds( SParticleMoveState const& state ) const
{
#ifdef _DEBUG // Debug particles out of static bounds
	static const Color3B clrDebug(0.99f, 0.01f, 0.15f);
	AABB const& bbStatic = GetContainer().GetStaticBounds();
	if (m_BaseColor != clrDebug && !bbStatic.IsReset() && GetContainer().StaticBoundsStable())
	{
		AABB bbPart(AABB::RESET);
		UpdateBounds(bbPart, state);
		if (!bbStatic.ContainsBox(bbPart))
		{
			AABB bbErr = bbPart;
			bbPart.ClipToBox(bbStatic);
			bbErr.min -= bbPart.min;
			bbErr.max -= bbPart.max;
			non_const(*this).m_BaseColor = clrDebug;
		}
	}
#endif
}

//
// Limit travel time to avoid excessive curvature.
//
SPU_NO_INLINE float CParticle::MoveLinear( SParticleMoveState& state, SCollisionInfo& coll, float fTime, SExternForces const& forces, ParticleTarget const& target, float fMaxLinearDev, float fMaxSlideDev, float fMinStepTime ) const
{
	// Clamp time based on turbulence params.
	ResourceParticleParams const& params = GetParams();

	if (coll.Sliding.IsSliding())
	{
		// Move slightly forward, to infer accelerations.
		SParticleMoveState stateTest = state;
		Move( stateTest, 0.01f, forces, target );

		Vec3 vExtAccel = (stateTest.m_Vel.vLin - state.m_Vel.vLin) / 0.01f;
		float fSlideTime = TravelSlide( state, coll.Sliding, fTime, vExtAccel, fMaxSlideDev, fMinStepTime );
		if (!coll.Sliding.IsSliding())
			coll.Hit.Clear();
		if (fSlideTime > 0.f)
			return fSlideTime;
	}

	SParticleMoveState stateNew = state;

	Move( stateNew, fTime, forces, target );

	if (fMaxLinearDev < fHUGE*0.5)
	{
		// If curved path encompasses inflection point relative to last collide normal, split in 2.
		float fNV0 = state.m_Vel.vLin * coll.Sliding.vNormal;
		float fNV1 = stateNew.m_Vel.vLin * coll.Sliding.vNormal;
		if (fNV0 * fNV1 < 0.f)
		{
			// Stop just after inflection point -- increase threshold for further tests.
			float fCorrect = abs(fNV0 / (fNV0 - fNV1));
			fTime *= min(fCorrect + 0.1f, 0.9f);
			stateNew = state;
			Move( stateNew, fTime, forces, target );
		}

		if (fTime > fMinStepTime)
		{
			Vec3 vDVel = stateNew.m_Vel.vLin - state.m_Vel.vLin;
			Vec3 vDPos = stateNew.m_Loc.t - state.m_Loc.t;
			float fDistSqr = vDPos.len2();
			float fDevSqrN = (vDVel.len2() * fDistSqr - sqr(vDVel*vDPos)) * sqr(fTime * 0.125f);
			if (fDevSqrN > fDistSqr * sqr(fMaxLinearDev))
			{
				// Exceeds linear threshold. Deviation proportional to t
				float fCorrect = fMaxLinearDev * isqrt_fast_tpl(fDevSqrN / fDistSqr);
				float fNewTime = max(fTime * fCorrect * 0.75f, fMinStepTime);
				if (fNewTime < fTime * 0.99f)
				{
					stateNew = state;
					fTime = fNewTime;
					Move( stateNew, fTime, forces, target );
				}
			}
		}
	}

	state = stateNew;
	return fTime;
}

SPU_NO_INLINE bool CParticle::SHitInfo::TestHit( ray_hit& hit, const Vec3& vPos0, const Vec3& vPos1, const Vec3& vVel0, const Vec3& vVel1, float fMaxDev, float fRadius ) const
{
	if (!HasPath())
		return false;

	if (HasHit())
	{
		// Test against collision plane.
		float fDist0 = (vPos0 - vPos) * vNormal;
		float fDist1 = (vPos1 - vPos) * vNormal - fRadius;
		if (fDist1 < 0.f)
		{
			if (fDist0 >= 0.f)
			{
				hit.dist = fDist0 / (fDist0 - fDist1);
				hit.pt.SetLerp(vPos0, vPos1, hit.dist);
			}
			else
			{
				hit.dist = 0.f;
				hit.pt = vPos;
			}

			// Find inflection point.
			float fV0 = vVel0 * vNormal,
						fV1 = vVel1 * vNormal;
			if (fV0 > 0.f && fV1 < 0.f)
			{
				/*
					c =  v0 + p0 - p1
					d = -v1 - p0 + p1

					p = p0 * (1-t) + p1 * t + (c * (1-t) + d * t) * t * (1-t) * 4
						= p0 + (p1-p0) t + (c + (d-c) t) t(1-t)
				*/
				float fMid = fV0 / (fV0 - fV1);
				Vec3 vVelMid; vVelMid.SetLerp(vVel0, vVel1, fMid);
				Vec3 vPosMid; vPosMid.SetLerp(vPos0, vPos1, fMid);
				vPosMid += (vPos0 - vPos1 + vVel0) * (fMid*(1.f-fMid)*(1.f-fMid));
				vPosMid += (vPos1 - vPos0 - vVel1) * (fMid*fMid*(1.f-fMid));
				float fDistMid = (vPosMid - vPos) * vNormal;
				if (fDistMid > 0.f)
				{
					float fDist = fDistMid / (fDistMid - fDist1);
					hit.pt.SetLerp(vPosMid, vPos1, fDist);
					hit.dist = fMid + (1.f - fMid) * fDist;
					assert(hit.dist >= 0.f && hit.dist <= 1.f);
				}
			}

			if ((hit.pt - vPos).len2() > sqr(fMaxDev))
			{
				hit.dist = 1.f;
				return false;
			}

			hit.n = vNormal;
			hit.pt += hit.n * fRadius;
			hit.surface_idx = nSurfaceIdx;
			hit.pCollider = pEntity;
			return true;
		}
	}

	// Test path proximity for hit or miss.
	hit.dist = 1.f;

	Vec3 vPosRel = vPos1 - vPos;
	float fT = vPosRel * vPathDir;
	if (fT <= fPathLength+fMaxDev)
		if (fT > -fMaxDev)
			if ((vPathDir * fT - vPosRel).len2() <= sqr(fMaxDev))
				return true;

	return false;
}

SPU_NO_INLINE bool CParticle::Collide( ray_hit& hit, float fStepTime, SParticleUpdateContext const& context, SExternForces const& forces, ParticleTarget const& target, const SParticleMoveState& stateNew, SCollisionInfo& collNew )
{
	hit.dist = 1.f;
	uint32 nCollideFlags = context.nEnvFlags & ENV_COLLIDE_ANY;
	if (nCollideFlags & ENV_COLLIDE_CACHE && GetCVars()->e_ParticlesObjectCollisions < 3)
	{
		// Cache collisions ahead in extended path.
		if (collNew.Hit.TestHit( hit, m_Loc.t, stateNew.m_Loc.t, m_Vel.vLin, stateNew.m_Vel.vLin, fMAX_COLLIDE_DEVIATION, fCOLLIDE_BUFFER_DIST ))
			nCollideFlags &= ~ENV_COLLIDE_CACHE;
		else
		{
			// Path does not match cache.
			// Require new check.
			collNew.Hit.Clear();
			float fTestTime = min((m_fEndAge - m_fAge) * 1.1f, context.fMaxLinearStepTime);
			if (fTestTime >= fStepTime * fMIN_TEST_AHEAD_MULT)
			{
				// Predict travel ahead, ignoring random movement params.
				SParticleMoveState stateTest = *this;
				SCollisionInfo collTest;
				if (m_pCollisionInfo)
					collTest = *m_pCollisionInfo;
				fTestTime = MoveLinear( stateTest, collTest, fTestTime, forces, target, fMAX_COLLIDE_DEVIATION, fHUGE, fStepTime );
				if (fTestTime >= fStepTime * fMIN_TEST_AHEAD_MULT && stateTest.m_Loc.t != m_Loc.t)
				{
					ray_hit hit_cache;
					GetContainer().GetCounts().ParticlesCollideTest += 1.f;
					if (SPhysEnviron::PhysicsCollision( hit_cache, m_Loc.t, stateTest.m_Loc.t, 0.f, nCollideFlags & ENV_COLLIDE_CACHE))
						collNew.Hit.SetHit( m_Loc.t, hit_cache.pt, hit_cache.n, hit_cache.surface_idx, hit_cache.pCollider );
					else
						collNew.Hit.SetMiss( m_Loc.t, stateTest.m_Loc.t );

					if (collNew.Hit.TestHit( hit, m_Loc.t, stateNew.m_Loc.t, m_Vel.vLin, stateNew.m_Vel.vLin, fMAX_COLLIDE_DEVIATION, fCOLLIDE_BUFFER_DIST ))
						nCollideFlags &= ~ENV_COLLIDE_CACHE;
				}
			}
		}
		if (hit.dist < 1.f)
			collNew.Clear();
#ifdef _DEBUG
		else if (!(nCollideFlags & ENV_COLLIDE_CACHE))
		{
			int& iDrawHelpers = gEnv->pPhysicalWorld->GetPhysVars()->iDrawHelpers;
			int iSave = iDrawHelpers; iDrawHelpers = 0;
			ray_hit hit2;
			if (SPhysEnviron::PhysicsCollision( hit2, m_Loc.t, stateNew.m_Loc.t, fCOLLIDE_BUFFER_DIST, context.nEnvFlags & ENV_COLLIDE_CACHE))
				GetContainer().GetCounts().ParticlesReject += 1.f;
			iDrawHelpers = iSave;
		}
#endif
	}
	if (nCollideFlags)
	{
		ray_hit hit2;
		GetContainer().GetCounts().ParticlesCollideTest += 1.f;
		if (SPhysEnviron::PhysicsCollision( hit2, m_Loc.t, stateNew.m_Loc.t, fCOLLIDE_BUFFER_DIST, nCollideFlags))
			if (hit2.dist < hit.dist)
				hit = hit2;
	}

	return hit.dist < 1.f;
}

void CParticle::InitAllocations(CParticleContainer* pCont)
{
	assert(pCont);
	assert(!m_pEmitter);
	assert(!m_aPosHistory);

	m_pContainer = pCont;

	if (pCont->GetChildFlags())
	{
		GetMain().CreateIndirectEmitters(pCont, this);
	}
	if (int nSteps = pCont->GetHistorySteps())
	{
		m_aPosHistory = new SParticleHistory[nSteps];
		for (int n = 0; n < nSteps; n++)
			m_aPosHistory[n].SetUnused();
	}
	if (pCont->NeedsCollisionInfo())
	{
		if (void *pMemory = ParticleAllocator::Allocate(m_pCollisionInfo))
		{		
			new (pMemory) SCollisionInfo;
		}
	}
}

SPU_NO_INLINE void CParticle::Init(SParticleUpdateContext const& context, float fAge, CParticleSubEmitter* pEmitter, const EmitParticleData& data)
{
	assert(pEmitter);
	m_pEmitter = pEmitter;
	assert(m_pContainer == &pEmitter->GetContainer());

	CParticleContainer& rContainer = GetContainer();
	ResourceParticleParams const& params = rContainer.GetParams();

	// Init all base values.
	float fEmissionStrength = pEmitter->GetStrength(-fAge);

	Start();
	if (params.fParticleLifeTime)
		m_fEndAge = params.fParticleLifeTime.GetVarValue(fEmissionStrength);

	m_nCollisionCount = 0;

	// Init base mods.
	m_BaseMods.Size = params.fSize.GetVarMod(fEmissionStrength);
	m_BaseMods.TailLength = params.fTailLength.GetVarMod(fEmissionStrength);
	m_BaseMods.Stretch = params.fStretch.GetVarMod(fEmissionStrength);

	m_BaseMods.AirResistance = params.fAirResistance.GetVarMod(fEmissionStrength);
	m_BaseMods.GravityScale = params.fGravityScale.GetVarMod(fEmissionStrength);
	m_BaseMods.Turbulence3DSpeed = params.fTurbulence3DSpeed.GetVarMod(fEmissionStrength);
	m_BaseMods.TurbulenceSize = params.fTurbulenceSize.GetVarMod(fEmissionStrength);
	m_BaseMods.TurbulenceSpeed = params.fTurbulenceSpeed.GetVarMod(fEmissionStrength);
	m_BaseMods.OrbitDistance = params.TargetAttraction.fOrbitDistance.GetVarMod(fEmissionStrength);

	m_BaseMods.LightSourceIntensity = params.LightSource.fIntensity.GetVarMod(fEmissionStrength);
	m_BaseMods.LightSourceRadius = params.LightSource.fRadius.GetVarMod(fEmissionStrength);
			
	m_BaseColor = params.cColor.GetVarValue(fEmissionStrength);
	m_BaseAlpha = params.fAlpha.GetVarMod(fEmissionStrength);

	m_nEmitterSequence = pEmitter->GetSequence();
	if (params.Connection)
		m_nTileVariant = uint8(pEmitter->GetEmitIndex());
	else
		m_nTileVariant = Random(params.TextureTiling.nVariantCount);

	IF (data.pPhysEnt && data.pStatObj, false)
	{
#if !defined(__SPU__)
		// Pre-generated physics entity.
		m_EmitGeom.m_pPhysEnt = data.pPhysEnt;
		m_EmitGeom.m_pPhysEnt->AddRef();
		m_EmitGeom.m_pStatObj = data.pStatObj;
		m_EmitGeom.m_pStatObj->AddRef();

		pe_params_foreign_data pfd;
		pfd.iForeignData = PHYS_FOREIGN_ID_RIGID_PARTICLE;
		pfd.pForeignData = m_EmitGeom.m_pStatObj;
		m_EmitGeom.m_pPhysEnt->SetParams(&pfd);

		// Get initial state.
		GetPhysicsState();

		m_Loc.s = params.fSize.GetMaxValue() * data.Location.s;



#endif
	}
	else
	{
		// Set geometry.
		m_EmitGeom.m_pStatObj = data.pStatObj ? data.pStatObj : (IStatObj*)params.pStatObj;
		if (m_EmitGeom.m_pStatObj)
			m_EmitGeom.m_pStatObj->AddRef();

		// Init location and velocity, overriding with data params if specified.
		if (!data.bHasLocation || !data.bHasVel)
		{
			InitPos( context, data.Location, fEmissionStrength );
		}
		if (data.bHasLocation)
		{
			m_Loc = data.Location;
		}
		if (data.bHasVel)
		{
			m_Vel = data.Velocity;
		}

		// Init history list.
		assert(!rContainer.GetHistorySteps() == !m_aPosHistory);
		for (int n = rContainer.GetHistorySteps()-1; n >= 0; n--)
			m_aPosHistory[n].SetUnused();

		IF ( params.ePhysicsType >= ParticlePhysics_SimplePhysics, false )
		{
			// Emitter-generated physical particles.
			Physicalize();
		}
	}

#if PARTICLE_MOTION_BLUR
	m_Prev.bValid = false;
#endif

	if (m_EmitGeom.m_pPhysEnt && GetMain().GetVisEnviron().OriginIndoors())
	{
#if !defined(__SPU__)
		pe_params_flags pf;
		pf.flagsOR = pef_ignore_ocean;
		m_EmitGeom.m_pPhysEnt->SetParams(&pf);


#endif
	}

	Update(context, fAge);
}

////////////////////////////////////////////////////////////////////////////
SPU_NO_INLINE void CParticle::InitPos( SParticleUpdateContext const& context, QuatTS const& loc, float fEmissionStrength )
{	
	ResourceParticleParams const& params = GetParams();
	SpawnParams spawnParams = GetMain().GetSpawnParams();
	SPhysEnviron const& PhysEnv = GetMain().GetPhysEnviron();

	assert(GetEmitter());
	CParticleSubEmitter const& rEmitter = *GetEmitter();

	// Position and orientation.
	m_Loc = loc;

	// Emit direction.
	m_Vel.vLin = GetNormal();

	if (params.bFocusGravityDir)
	{
		float fLenSq = PhysEnv.m_UniformForces.vAccel.GetLengthSquared();
		if (fLenSq > FLT_MIN)
		{
			m_Vel.vLin = PhysEnv.m_UniformForces.vAccel * -isqrt_tpl(fLenSq);
			if (params.bFocusRotatesEmitter)
				m_Loc.q = Quat::CreateRotationV0V1( GetNormal(), m_Vel.vLin ) * m_Loc.q;
		}
	}
	if (params.fFocusAngle)
	{
		float fAngle = DEG2RAD(params.fFocusAngle.GetVarValue(fEmissionStrength, rEmitter.GetChaosKey()));
		float fAzimuth = DEG2RAD(params.fFocusAzimuth.GetVarValue(fEmissionStrength, rEmitter.GetChaosKey()));
		Quat qFocus = m_Loc.q * Quat::CreateRotationXYZ( Ang3(fAngle, fAzimuth, 0.f) );
		m_Vel.vLin = qFocus.GetColumn1();
		if (params.bFocusRotatesEmitter)
			m_Loc.q = qFocus;
	}

	// Emit geom.
	CParticleSource& Source = m_pEmitter->GetSource();
	if (Source.GetEmitGeom())
	{
		EGeomType eAttachType = params.eSpawnIndirection ? +params.eAttachType : spawnParams.eAttachType;
		if (eAttachType != GeomType_None)
		{
			EGeomForm	eAttachForm = params.eSpawnIndirection ? +params.eAttachForm : spawnParams.eAttachForm;
			PosNorm ran = { m_Loc.t, m_Vel.vLin };
			GetRandomPos( ran, Source.GetEmitGeom(), eAttachForm, loc );
			m_Loc.t = ran.vPos;
			m_Vel.vLin = ran.vNorm;
		}
	}

	// Offsets.
	Vec3 vOffset;
	for (;;)
	{
		vOffset = BiRandom(params.vRandomOffset);
		if (params.fOffsetRoundness)
		{
			// Loop until random point is within rounded volume.
			Vec3 vR( max(abs(vOffset.x) - context.vEmitBox.x, 0.f), max(abs(vOffset.y) - context.vEmitBox.y, 0.f), max(abs(vOffset.z) - context.vEmitBox.z, 0.f) );
			if (sqr(vR.x * context.vEmitScale.x) + sqr(vR.y * context.vEmitScale.y) + sqr(vR.z * context.vEmitScale.z) > 1.f)
				continue;
		}

		// Apply inner volume subtraction.
		if (params.fOffsetInnerScale)
		{
			// Find max possible scaled offset.
			float fScaleMax = div_min( params.vRandomOffset.x, abs(vOffset.x), div_min( params.vRandomOffset.y, abs(vOffset.y), div_min(params.vRandomOffset.z, abs(vOffset.z), fHUGE) ) );
			Vec3 vOffsetMax = vOffset * fScaleMax;
			if (params.fOffsetRoundness)
			{
				Vec3 vRMax( max(abs(vOffsetMax.x) - context.vEmitBox.x, 0.f), max(abs(vOffsetMax.y) - context.vEmitBox.y, 0.f), max(abs(vOffsetMax.z) - context.vEmitBox.z, 0.f) );
				float fRMaxSqr = sqr(vRMax.x * context.vEmitScale.x) + sqr(vRMax.y * context.vEmitScale.y) + sqr(vRMax.z * context.vEmitScale.z);
				if (fRMaxSqr > 0.f)
				{
					vRMax *= isqrt_tpl(fRMaxSqr);
					for (int a = 0; a < 3; a++)
					{
						if (vRMax[a] > 0.f)
							vOffsetMax[a] = (context.vEmitBox[a] + vRMax[a]) * fsgnf(vOffsetMax[a]);
					}
				}
			}

			// Interpolate between current ans max offsets.
			vOffset += (vOffsetMax - vOffset) * params.fOffsetInnerScale;
		}

		break;
	}

	if (params.bEmitOffsetDir && !vOffset.IsZero())
	{
		m_Vel.vLin = m_Loc.q * vOffset.GetNormalized();
	}

	vOffset += params.vPositionOffset;

	// To world orientation/scale.
	m_Loc.t += m_Loc.q * vOffset * loc.s;

	// Size.
	m_Loc.s *= params.fSize.GetValueFromMod(m_BaseMods.Size, 0.f);

	// Velocity.
	float fPhi = params.fEmitAngle.GetVarValue(fEmissionStrength);
	if (fPhi > 0.f)
	{
		//
		// Adjust angle to create a uniform distribution.
		//
		//		density distribution d(phi) = sin phi
		//		cumulative density c(phi) = Int(0,phi) sin x dx = 1 - cos phi
		//		normalised cn(phi) = (1 - cos phi) / (1 - cos phiMax)
		//		reverse function phi(cn) = acos_tpl(1 + cn(cos phiMax - 1))
		//

		float fPhiMax = params.fEmitAngle.GetMaxValue();
		fPhi /= fPhiMax;
		fPhi = cry_acosf( 1.f + fPhi * (cry_cosf(DEG2RAD(fPhiMax))-1.f) );

		float fTheta = Random(DEG2RAD(360));

		float fPhiCS[2], fThetaCS[2];
		sincos_tpl(fPhi, &fPhiCS[1], &fPhiCS[0]);
		sincos_tpl(fTheta, &fThetaCS[1], &fThetaCS[0]);

		// Compute perpendicular bases.
		Vec3 vX;
		if (m_Vel.vLin.z != 0.f)
			vX( 0.f, -m_Vel.vLin.z, m_Vel.vLin.y );
		else
			vX( -m_Vel.vLin.y, m_Vel.vLin.x, 0.f );
		vX.NormalizeFast();
		Vec3 vY = m_Vel.vLin ^ vX;

		m_Vel.vLin = m_Vel.vLin * fPhiCS[0] + (vX * fThetaCS[0] + vY * fThetaCS[1]) * fPhiCS[1];
	}

	// Speed.
	float fSpeed = params.fSpeed.GetVarValue(fEmissionStrength);
	m_Vel.vLin *= fSpeed * spawnParams.fSpeedScale * loc.s;
	if (!params.eMoveRelEmitter)
		// Add centrifugal speed.
		m_Vel.vLin += Source.GetVelocity().VelocityAt(m_Loc.t - m_pEmitter->GetWorldPivot()) * params.fInheritVelocity;

	// Initial orientation.
	if (params.eFacing == ParticleFacing_Free || params.eFacing == ParticleFacing_Decal)
	{
		// 3D absolute rotation. Compute quat from init angles, convert to world space.
		Vec3 vAngles = DEG2RAD( params.vInitAngles + BiRandom(params.vRandomAngles) );
		m_Vel.vRot = m_Loc.q * DEG2RAD( params.vRotationRate + BiRandom(params.vRandomRotationRate) );
		m_Loc.q = m_Loc.q * Quat::CreateRotationXYZ( Ang3(vAngles) );
	}
	else
	{
		// 2D relative rotation about Y.
		m_fAngle = DEG2RAD( params.vInitAngles.y + BiRandom(+params.vRandomAngles.y) );
		m_Vel.vRot.y = DEG2RAD( params.vRotationRate.y + BiRandom(+params.vRandomRotationRate.y) );
	}

	// Rotate to correct alignment.
	UpdateRotation(*this);
	DebugBounds(*this);
}

SPU_NO_INLINE void CParticle::UpdateBounds( AABB& bb, SParticleMoveState const& state ) const
{
	ParticleParams const& params = GetParams();
	float fRelativeAge = state.GetRelativeAge();
	float fRadius = state.m_Loc.s * GetBaseRadius();
	bb.Add(state.m_Loc.t, fRadius);

	if (params.fStretch)
	{
		// Stretch along delta direction.
		float fStretch = params.fStretch.GetValueFromMod(m_BaseMods.Stretch, fRelativeAge);
		bb.Add(state.m_Loc.t + state.m_Vel.vLin * (fStretch * max(1.f + params.fStretch.fOffsetRatio, 0.f)), fRadius);
	}
	if (m_aPosHistory && m_aPosHistory[0].IsUsed())
	{
		// Add oldest element.
		bb.Add(m_aPosHistory[0].Loc.t, fRadius);
	}
}

#ifdef PARTICLE_EDITOR_FUNCTIONS
bool CParticle::NeedReallocation() const
{
	if (GetContainer().GetHistorySteps() != GetParams().fTailLength.nTailSteps)
		return true;
	if (GetContainer().NeedsCollisionInfo() && !m_pCollisionInfo)
		return true;
	return false;
}
#endif

SPU_NO_INLINE void CParticle::AddPosHistory( SParticleMoveState const& stateNew )
{
	// Possibly store current position in history. Check significance against previous positions and stateNew.
	float fRelativeAge = GetRelativeAge();
	float fTailLength = GetParams().fTailLength.GetValueFromMod(m_BaseMods.TailLength, fRelativeAge);

	int nCount = GetContainer().GetHistorySteps();
	while (nCount > 0 && !m_aPosHistory[nCount-1].IsUsed())
		nCount--;

	// Clear out old entries.
	float fMinAge = m_fAge - fTailLength;
	int nStart = 0;
	while (nStart+1 < nCount && m_aPosHistory[nStart+1].fAge < fMinAge)
		nStart++;

	if (nStart > 0)
	{
		for (int n = nStart; n < nCount; n++)
			m_aPosHistory[n-nStart] = m_aPosHistory[n];
		for (int n = nCount-nStart; n < nCount; n++)
			m_aPosHistory[n].SetUnused();
		nCount -= nStart;
	}

	if (nCount == GetContainer().GetHistorySteps())
	{
		// Remove least significant entry.
		int nLeast = nCount;
		float fLeastSignif = fHUGE;
		for (int n = 1; n <= nCount; n++)
		{
			QuatTS aqts[3];
			aqts[0] = m_aPosHistory[n-1].Loc;

			if (n < nCount)
				aqts[1] = m_aPosHistory[n].Loc;
			else
				aqts[1] = m_Loc;

			if (n+1 < nCount)
				aqts[2] = m_aPosHistory[n+1].Loc;
			else if (n+1 == nCount)
				aqts[2] = m_Loc;
			else
				aqts[2] = stateNew.m_Loc;

			float fSignif = InterpVariance( aqts[0], aqts[1], aqts[2] );
			if (fSignif < fLeastSignif)
			{
				fLeastSignif = fSignif;
				nLeast = n;
			}
		}
		if (nLeast != nCount)
		{
			// Delete entry.
			for (int n = nLeast; n < nCount-1; n++)
				m_aPosHistory[n] = m_aPosHistory[n+1];
			nCount--;
		}
	}
	if (nCount < GetContainer().GetHistorySteps())
	{
		m_aPosHistory[nCount].fAge = m_fAge;
		m_aPosHistory[nCount].Loc = m_Loc;
	}
}

SPU_NO_INLINE void CParticle::Update( SParticleUpdateContext const& context, float fFrameTime )
{
	const float fTIME_EPSILON = 1e-5f;

	// Detect deactivated emitters.
	if (m_pEmitter)
	{
		if (m_pEmitter->GetSequence() != m_nEmitterSequence)
			m_pEmitter = 0;
		else if (!m_pEmitter->IsAlive())
			m_pEmitter = 0;
	}

	CParticleContainer& rContainer = GetContainer();
	SPhysEnviron const& PhysEnv = GetMain().GetPhysEnviron();
	ResourceParticleParams const& params = rContainer.GetParams();

	// Process only within lifetime of particle.
	if (m_fAge + fFrameTime <= 0.f)
	{
		m_fAge += fFrameTime;
		fFrameTime = 0.f;
	}
	else if (m_fAge < 0.f)
	{
		fFrameTime += m_fAge;
		m_fAge = 0.f;
	}

	if (!params.bRemainWhileVisible)
	{
		assert(m_fAge <= m_fEndAge);
		fFrameTime = min(fFrameTime, m_fEndAge - m_fAge);
	}

	////////////////////////////////////////////////////////////////////////////////////////////////
	// Move
	////////////////////////////////////////////////////////////////////////////////////////////////

  IF (m_EmitGeom.m_pPhysEnt, false)
  {
#if !defined(__SPU__) // phys entities of particles are not supported on SPU, so no need to scan/simulate this code
		// Use physics engine to move particles.
		GetPhysicsState();

		// Get collision status.
		pe_status_collisions status;
		coll_history_item item;
		status.pHistory = &item;
		status.age = 1.f;
		status.bClearHistory = 1;

		if (m_EmitGeom.m_pPhysEnt->GetStatus(&status) > 0)
		{
			if (m_nCollisionCount < 255)
				m_nCollisionCount++;
			SParticleMoveState::Collide(m_fAge);
			if (params.fBounciness < 0)
			{
				// Kill particle on collision.
				End();
				return;
			}
		}


#endif
  }
	else
	{
		uint32 nFlags = context.nEnvFlags & (ENV_GRAVITY | ENV_WIND);
		uint32 nCollideFlags = context.nEnvFlags & ENV_COLLIDE_ANY;
		float fMaxStepTime = fFrameTime;

		// Transform emitter-relative particles by emitter movement.
		if (params.eMoveRelEmitter && m_pEmitter)
		{
			QuatTS qtMove;
			if (m_pEmitter->GetMoveRelative(qtMove))
			{
				Transform(qtMove);
				if (params.eMoveRelEmitter == MoveRelative_YesWithTail && GetContainer().GetHistorySteps())
				{
					for (SParticleHistory* pPos = m_aPosHistory + GetContainer().GetHistorySteps()-1; pPos >= m_aPosHistory; pPos--)
					{
						if (pPos->IsUsed())
							pPos->Loc = qtMove * pPos->Loc;
					}
				}
			}
		}

		// Get particle target.
		ParticleTarget target = GetMain().GetTarget();
		if (GetContainer().HasLocalTarget() && m_pEmitter && !target.bPriority)
			m_pEmitter->GetLocalTarget(target);
		else if (params.TargetAttraction.bIgnore)
			target.bTarget = false;

		while (fFrameTime > 0.f)
		{
			// Apply previously computed MaxStepTime.
			float fStepTime = min(fFrameTime, fMaxStepTime);
			fMaxStepTime = fFrameTime;

			SExternForces forces(ZERO);
			if (nFlags)
				PhysEnv.CheckPhysAreas( m_Loc.t, forces.vAccel, forces.vWind, nFlags );

			SParticleMoveState stateNew = *this;

			stateNew.m_Loc.s = params.fSize.GetValueFromMod(m_BaseMods.Size, GetRelativeAge(fStepTime)) * GetMain().GetParticleScale();
			
			SCollisionInfo collNew;
			if (m_pCollisionInfo)
				collNew = *m_pCollisionInfo;

			fStepTime = MoveLinear( stateNew, collNew, min(fStepTime, context.fMaxLinearStepTime), 
				forces, target, context.fMaxLinearDeviation, context.fMaxLinearDeviation, context.fMinStepTime );

			Vec3 vMove = stateNew.m_Loc.t - m_Loc.t;

			if (nFlags & PhysEnv.m_nNonUniformFlags && fStepTime > context.fMinStepTime)
			{
				// Subdivide travel in non-uniform areas.
				float fMoveSqr = vMove.GetLengthSquared();
				if (fMoveSqr > sqr(fMAX_NONUNIFORM_TRAVEL))
				{
					SExternForces forces2;
					PhysEnv.CheckPhysAreas( stateNew.m_Loc.t, forces2.vAccel, forces2.vWind, nFlags );

					// Estimate acceleration difference.
					Vec3 vErrorEst = forces2.vAccel - forces.vAccel;
					if (nFlags & ENV_WIND)
					{
						float fDrag = params.fAirResistance.GetValueFromMod(m_BaseMods.AirResistance, GetRelativeAge(fStepTime*0.5f)); 
						vErrorEst += (forces2.vWind - forces.vWind) * fDrag;
					}

					float fDevSqr = vErrorEst.GetLengthSquared() * sqr( sqr(fStepTime)*0.5f );
					if (fDevSqr > sqr(0.75f*fMAX_NONUNIFORM_TRAVEL))
					{
						float fShorten = 0.75f*fMAX_NONUNIFORM_TRAVEL * isqrt_tpl(fDevSqr);
						fShorten = min(fShorten, 0.75f);
						fMaxStepTime = max(fStepTime * fShorten, context.fMinStepTime);
						if (fMaxStepTime < fStepTime * 0.99f)
							continue;
					}
				}
			}

			if (params.bSpaceLoop)
			{
				// Wrap the particle into camera-aligned BB.
				Vec3 vPosSpaceLoop = stateNew.m_Loc.t - context.SpaceLoop.vCenter;

				for (int a = 0; a < 3; a++)
				{
					float fPosSpace = vPosSpaceLoop * context.SpaceLoop.vScaledAxes[a];
					if (abs(fPosSpace) > 1.f)
					{
						float fCorrect = floor_tpl(fPosSpace);
						fCorrect = if_neg_else(fCorrect, fCorrect, fCorrect+1.f);
						stateNew.m_Loc.t -= context.SpaceLoop.vScaledAxes[a] * (fCorrect * sqr(context.SpaceLoop.vSize[a]));

					}
				}

				DebugBounds(stateNew);
			}

			////////////////////////////////////////////////////////////////////////////////////////////////
			// Simple collision with physics entities
			// Collision strategy:
			//	* Move, with sliding from previous collisions
			//	* Test collision, determine bounce or sliding
			//	* Set sliding state for next frames, within slide distance
			////////////////////////////////////////////////////////////////////////////////////////////////

			if (nCollideFlags && 
				(params.nMaxCollisionEvents == 0 || m_nCollisionCount < params.nMaxCollisionEvents) && 
				!vMove.IsZero())
			{
				ray_hit hit;
				if (Collide( hit, fStepTime, context, forces, target, stateNew, collNew ))
				{
					assert(hit.dist >= 0.f);
					rContainer.GetCounts().ParticlesCollideHit += 1.f;

					// Set particle to collision point.
					// Linearly interpolate velocity based on distance.
					stateNew.m_Vel.vLin.SetLerp(m_Vel.vLin, stateNew.m_Vel.vLin, hit.dist);
					stateNew.m_Loc.t = hit.pt;

					fStepTime *= hit.dist;

					// Rotate to surface normal.
					RotateTo(stateNew.m_Loc.q, hit.n);
					stateNew.Collide(m_fAge + fStepTime - fFrameTime);

					if (hit.dist <= 0.f)
						// Disable further collisions this frame
						nCollideFlags = 0;

					if (params.fBounciness < 0.f)
					{
						// Particle dies on collision.
						GetMoveState() = stateNew;
						m_fAge += fStepTime;
						End();
						return;
					}
					else
					{
						// Remove perpendicular velocity component.
						float fVelPerp = stateNew.m_Vel.vLin * hit.n;
						if (fVelPerp <= 0.f)
						{
							if (m_aPosHistory)
							{
								// Store double history steps at bounce.
								UpdateRotation(stateNew, fStepTime);
								stateNew.m_fAge += fStepTime;
								if (params.eFacing != ParticleFacing_Free)
									m_fAngle += m_Vel.vRot.y * fStepTime;
								AddPosHistory(stateNew);
								GetMoveState() = stateNew;
								fFrameTime -= fStepTime;
								fStepTime = 0.f;
							}

							stateNew.m_Vel.vLin -= hit.n * fVelPerp;

							// Get phys params from material, or particle params.
							float fBounce, fSlidingFriction;
							GetCollisionParams(hit.surface_idx, fBounce, fSlidingFriction);

							float fVelBounce = fVelPerp * -fBounce;
							float fAccelPerp = forces.vAccel * hit.n;

							// Sliding occurs when the bounce distance would be less than the collide buffer.
							// d = - v^2 / 2a <= dmax
							// v^2 <= -dmax*2a
							if (sqr(fVelBounce) <= fCOLLIDE_BUFFER_DIST * fAccelPerp * -2.f)
							{
								collNew.Sliding.SetSliding(hit.pCollider, hit.n, fSlidingFriction);
								if (m_nCollisionCount == 0)
									m_nCollisionCount++;
							}
							else
							{
								// Bouncing.
								collNew.Sliding.ClearSliding(hit.n);
								if (m_nCollisionCount < 255)
									m_nCollisionCount++;

								// Bounce the particle, continue for remainder of frame.
								stateNew.m_Vel.vLin += hit.n * fVelBounce;
							}
						}
						else
							// Disable further collisions this frame
							nCollideFlags = 0;
					}
				}
			}

			UpdateRotation(stateNew, fStepTime);

			// Store computed state.
			if (m_aPosHistory)
				AddPosHistory(stateNew);

			GetMoveState() = stateNew;
			if (m_pCollisionInfo)
				*m_pCollisionInfo = collNew;

			if (params.eFacing != ParticleFacing_Free)
				m_fAngle += m_Vel.vRot.y * fStepTime;

			fFrameTime -= fStepTime;
			m_fAge += fStepTime;
		}

		rContainer.GetCounts().ParticlesReiterate -= 1.f;
  }

	m_fAge += max(fFrameTime, 0.f);

	// Shrink particle when approaching visible limits.
	if (params.tVisibleUnderwater != Trinary_Both)
	{
		// Particles not allowed to cross water boundary, shrink them as they get close.
		if (PhysEnv.m_tUnderWater == Trinary_Both)
		{
			float fSense = (params.tVisibleUnderwater == Trinary_If_False ? 1.f : -1.f);
			float fBase = GetBaseRadius();
			float fRadius = m_Loc.s * fBase;
			Plane pl;
			float fWaterDist = PhysEnv.DistanceFromWater(pl, m_Loc.t, fSense, fRadius);
			if (fWaterDist < fRadius)
			{
				if (fWaterDist <= 0.f)
				{
					m_Loc.s = 0.f;
					if (!params.bSpaceLoop)
						// Kill particle when it crosses water, unless recycled in space loop.
						End();
				}
				else
					m_Loc.s = fWaterDist / fBase;
			}
		}
		else if (PhysEnv.m_tUnderWater != params.tVisibleUnderwater)
			End();
	}

	// Update dynamic bounds if required.
	if (context.pbbDynamicBounds)
		UpdateBounds(*context.pbbDynamicBounds);
}

SPU_NO_INLINE void CParticle::UpdateRotation( SParticleMoveState& state, float fStepTime ) const
{
	ResourceParticleParams const& params = GetParams();

	// Apply rotation velocity.
	if (fStepTime != 0.f)
	{
		if (params.eFacing == ParticleFacing_Free || params.eFacing == ParticleFacing_Decal)
		{
			// 3D rotation.
			float fRotVelSqr = m_Vel.vRot.GetLengthSquared();
			if (fRotVelSqr > FLT_MIN)
			{
				float fRotVel = sqrt_fast_tpl(fRotVelSqr);
				state.m_Loc.q = Quat::CreateRotationAA(fRotVel * fStepTime, m_Vel.vRot / fRotVel) * state.m_Loc.q;
			}
		}
	}

	// Orient in world space as required.
	if (params.bOrientToVelocity || m_aPosHistory)
	{
		// Orient in velocity direction.
		float fLenSq = state.m_Vel.vLin.GetLengthSquared();
		if (fLenSq > FLT_MIN)
		{
			state.m_Loc.q = Quat::CreateRotationV0V1( state.m_Loc.q.GetColumn2(), state.m_Vel.vLin * isqrt_tpl(fLenSq)) * state.m_Loc.q;
			state.m_Loc.q.NormalizeSafe();
		}
	}

	switch (params.eFacing)
	{
		case ParticleFacing_Water:
		{
			// Project point and velocity onto plane.
			Plane plWater;
			float fDist = GetMain().GetPhysEnviron().DistanceFromWater(plWater, state.m_Loc.t, 1.f, -WATER_LEVEL_UNKNOWN);
			if (plWater.d < -WATER_LEVEL_UNKNOWN)
			{
				#if !defined(__SPU__)
					if (GetCVars()->e_ParticlesQuality >= 3)
						if (plWater.d == -m_pTerrain->GetWaterLevel())
							fDist -= COcean::GetWave( m_Loc.t, Cry3DEngineBase::GetFrameID() );
				#endif
				state.m_Loc.t -= plWater.n * (fDist - params.vPositionOffset.z - fATTACH_BUFFER_DIST);
				state.m_Vel.vLin -= plWater.n * (state.m_Vel.vLin * plWater.n);
				RotateTo(state.m_Loc.q, plWater.n);
			}
			else
			{
				// No water, kill it.
				state.End();
			}
			break;
		}
		case ParticleFacing_Terrain:
		{
			if (GetTerrain())
			{
				// Project center and velocity onto plane.
				if (state.m_Loc.s > 0.f)
				{
					// Sample terrain around point to get heights and average normal.
					Vec3 avCorner[5];
					for (int c = 0; c < 5; c++)
					{
						avCorner[c] = state.m_Loc.t;
						if (c < 4)
						{
							avCorner[c].x += ((c&1)*2-1) * state.m_Loc.s;
							avCorner[c].y += ((c&2)-1) * state.m_Loc.s;
						}
						// explicit pass false to the GetZApr function, which means to not include voxels
						// if you change this, please also look at the SPU code, since the voxel interaction part is
						// currently disabled for SPUs
						avCorner[c].z = GetTerrain()->GetZApr(avCorner[c].x, avCorner[c].y, false, GetDefSID());
					}

					// Rotate sprite to average normal of quad.
					Vec3 vTNorm = (avCorner[3] - avCorner[0]) ^ (avCorner[2] - avCorner[1]);
					if (vTNorm.z != 0.f)
					{
						if (vTNorm.z < 0.f)
							vTNorm = -vTNorm;
						vTNorm.Normalize();
						RotateTo(state.m_Loc.q, vTNorm);

						// Adjust z to match plane.	
						float fZAdd = -fHUGE;
						for (int c = 0; c < 5; c++)
							fZAdd = max(fZAdd, avCorner[c] * vTNorm);
						state.m_Loc.t.z += (fZAdd - state.m_Loc.t * vTNorm) / vTNorm.z + fATTACH_BUFFER_DIST; 
					}
				}
			}
			break;
		}
		case ParticleFacing_Velocity:
		{
			// Rotate to movement dir.
			float fLenSq = state.m_Vel.vLin.GetLengthSquared();
			if (fLenSq > FLT_MIN)
				RotateTo(state.m_Loc.q, state.m_Vel.vLin * isqrt_tpl(fLenSq));
			break;
		}
	}
}

SPU_NO_INLINE Vec3 CParticle::VortexRotation(SParticleMoveState& state, float fTime) const
{
	// Compute vortex rotational offset at current age.
	// p(t) = TSize e^(i TSpeed t) (t max 1)
	const ParticleParams& params = GetParams();
	
	CIntToFloat convTurbulenceSpeed	(m_BaseMods.TurbulenceSpeed.GetCompressedValue());
	CIntToFloat convTurblenceSize		(m_BaseMods.TurbulenceSize.GetCompressedValue());

	float fRelativeAge = state.GetRelativeAge(fTime);
	float fVortexSpeed = DEG2RAD( params.fTurbulenceSpeed.GetValueFromMod(m_BaseMods.TurbulenceSpeed.InflateValue(convTurbulenceSpeed.Convert()), fRelativeAge ));
	float fAngle = fVortexSpeed * (state.GetAge() + fTime);
	float fVortexSize = params.fTurbulenceSize.GetValueFromMod( m_BaseMods.TurbulenceSize.InflateValue(convTurblenceSize.Convert()), fRelativeAge ) * GetMain().GetParticleScale();

	Vec2 vRot;
	cry_sincosf( fAngle, &vRot.y, &vRot.x );

	// Scale down vortex size in first half rotation.
	fVortexSize *= div_min(abs(fAngle), gf_PI, 1.f);
	vRot *= fVortexSize;

	Vec3 const& vAxis = state.m_Vel.vLin;
	Vec3 vX( vAxis.y, -vAxis.x, 0.f);
	if (!vX.IsZero())
	{
		Vec3 vY = vX.cross(vAxis);
		vX.NormalizeFast();
		vY.NormalizeFast();
		return vX * vRot.x + vY * vRot.y;
	}
	else
	{
		return Vec3(vRot);
	}
}

SPU_NO_INLINE void CParticle::TargetMovement( SParticleMoveState& state, float fTime, float fRelativeAge, ParticleTarget const& target ) const
{
	const ParticleParams& params = GetParams();

	float fTargetRadius = max( params.TargetAttraction.fOrbitDistance.GetValueFromMod(m_BaseMods.OrbitDistance, fRelativeAge) + target.fRadius, 0.f);
	bool bOrbiting = params.TargetAttraction.bOrbit && fTargetRadius > 0.f;

	state.m_Loc.t -= state.m_Vel.vLin * fTime;

	// Decompose current velocity into radial+angular components.
	Vec3 vPos = state.m_Loc.t - target.vTarget;
	Vec3 vVel = state.m_Vel.vLin - target.vVelocity;

	float fDist = vPos.GetLength();
	float fVel = vVel.GetLength();
	Vec3 vRadialDir = vPos.GetNormalized();
	float fRadialVel = vVel | vRadialDir;
	Vec3 vOrbitalVel = vVel - vRadialDir * fRadialVel;
	Vec3 vOrbitalDir = vOrbitalVel.GetNormalized();
	float fOrbitalVel = vOrbitalVel.GetLength();

	// Determine arrival time.
	float fArrivalTime = div_min(fTargetRadius - fDist, fRadialVel, fHUGE);
	if (fArrivalTime < 0.f)
		fArrivalTime = fHUGE;

	// Goal is to reach target radius in a quarter revolution over particle's life.
	float fLife = state.m_fEndAge - state.m_fAge;
	fArrivalTime = div_min(gf_PI*0.5f*fDist * fLife, fOrbitalVel * state.m_fEndAge, fArrivalTime);

	if (fArrivalTime > fLife)
	{
		if (params.TargetAttraction.bExtendSpeed)
			fArrivalTime = fLife;
	}
	else if (!bOrbiting)
	{
		// Age particle prematurely based on target time.
		state.m_fEndAge = state.m_fAge + fArrivalTime;
	}

	// Change particle direction, maintaining speed.
	fRadialVel = (fTargetRadius - fDist) / (fArrivalTime + FLT_MIN);
	fOrbitalVel = sqrt_tpl( max(0.f, sqr(fVel) - sqr(fRadialVel)) );

	// Execute the orbit.
	float fNewDist = fDist + fRadialVel * min(fTime, fArrivalTime);

	if (fNewDist > 0.f)
	{
		// Orbital travel needed.
		// Compute angular movement, using geometric mean of start and end radii, which is a close approximation.
		float fVelAngle = fOrbitalVel * isqrt_tpl(fDist * fNewDist) * fTime;

		float fCos, fSin;
		sincos_tpl(fVelAngle, &fSin, &fCos);
		state.m_Loc.t += (vRadialDir * (fCos - 1.f) + vOrbitalDir * fSin) * fDist;

		// Recompute polar axes.
		vRadialDir = (state.m_Loc.t - target.vTarget).GetNormalized();
		vOrbitalDir -= vRadialDir * (vRadialDir | vOrbitalDir);
		vOrbitalDir.Normalize();
	}

	state.m_Loc.t += vRadialDir * (fNewDist - fDist);

	state.m_Vel.vLin = vRadialDir * fRadialVel + vOrbitalDir * fOrbitalVel + target.vVelocity;

	if (params.TargetAttraction.bShrink)
	{
		fDist = fNewDist - fTargetRadius;
		if (fDist < state.m_Loc.s * GetBaseRadius())
			state.m_Loc.s = max(fDist, 0.f) / GetBaseRadius();
	}
	assert(m_Loc.s >= 0.f);
}

SPU_NO_INLINE void CParticle::GetCollisionParams(int nCollSurfaceIdx, float& fBounce, float& fDrag) const
{
	// Get phys params from material, or particle params.
	fBounce = GetParams().fBounciness;
	fDrag = GetParams().fDynamicFriction;

	IMaterialManager* pMatMan = SPU_MAIN_PTR(GetMatMan());

	int iSurfaceIndex = GetSurfaceIndex();
	if (iSurfaceIndex > 0)
	{
		ISurfaceType* pSurfPart = pMatMan->GetSurfaceType(iSurfaceIndex);
		if (pSurfPart)
		{
			ISurfaceType::SPhysicalParams const& physPart = SPU_MAIN_PTR(pSurfPart)->GetPhyscalParams();
			fBounce = max(fBounce, physPart.bouncyness);
			fDrag = max(fDrag, physPart.friction);
		}
	}

	// Combine with hit surface params.
	ISurfaceType *pSurfHit = pMatMan->GetSurfaceType(nCollSurfaceIdx);
	if (!pSurfHit)
		pSurfHit = SPU_MAIN_PTR( pMatMan->GetDefaultTerrainLayerMaterial() )->GetSurfaceType();
	ISurfaceType::SPhysicalParams const& physHit = SPU_MAIN_PTR(pSurfHit)->GetPhyscalParams();

	fBounce = clamp_tpl( (fBounce + physHit.bouncyness)*0.5f, 0.f, 1.f );
	fDrag = max( (fDrag + physHit.friction)*0.5f, 0.f );
}

SPU_NO_INLINE void CParticle::GetRenderLocation( PivotLocation& loc ) const
{
	loc = m_Loc;
	if (m_EmitGeom.m_pStatObj && !GetParams().bNoOffset && GetParams().ePhysicsType != ParticlePhysics_RigidBody)
	{
		// Recenter object pre-rotation.
		loc.pivot = m_EmitGeom.m_pStatObj->GetAABB().GetCenter();
		loc.t = loc * -loc.pivot;
	}
}

SPU_NO_INLINE void CParticle::Deactivate()
{
	m_fAge = m_fEndAge;

  IF (m_EmitGeom.m_pPhysEnt, 0)
	{
		GetPhysicalWorld()->DestroyPhysicalEntity(m_EmitGeom.m_pPhysEnt);
	}

	IF (m_pCollisionInfo, 0)
	{
		m_pCollisionInfo->Clear();
	}

	SetEmitGeom(GeomRef());
}

CParticle::~CParticle()
{
  IF (m_EmitGeom.m_pPhysEnt, 0)
	{
		GetPhysicalWorld()->DestroyPhysicalEntity(m_EmitGeom.m_pPhysEnt);
	}

	delete[] m_aPosHistory;
	ParticleAllocator::Delete(m_pCollisionInfo);
}

char GetMinAxis(Vec3 const& vVec)
{
	float x = fabs(vVec.x);
	float y = fabs(vVec.y);
	float z = fabs(vVec.z);

	if (x<y && x<z)
		return 'x';
	
	if (y<x && y<z)
		return 'y';
	
	return 'z';
}

#define fSPHERE_VOLUME float(4.f / 3.f * gf_PI)

SPU_NO_INLINE int CParticle::GetSurfaceIndex() const
{
	ResourceParticleParams const& params = GetParams();
	if (params.sSurfaceType.nIndex)
		// Explicit surface type.
		return params.sSurfaceType.nIndex;
	if (params.pMaterial)
	{
		// Get from material, if defined.
		if (int index = params.pMaterial->GetSurfaceTypeId())
			return index;
	}
	if (m_EmitGeom.m_pStatObj)
	{
		// Get from geometry.
		if (phys_geometry* pGeom = m_EmitGeom.m_pStatObj->GetPhysGeom())
		{
			if (pGeom->surface_idx < pGeom->nMats)
				return pGeom->pMatMapping[pGeom->surface_idx];
			else
				return pGeom->surface_idx;
		}
	}
	return 0;
}

void CParticle::Physicalize()
{
#if !defined(__SPU__) // never called on spu, so exclude to spare code size 
	ResourceParticleParams const& params = GetParams();
	SPhysEnviron const& PhysEnv = GetMain().GetPhysEnviron();

	Vec3 vGravity = PhysEnv.m_UniformForces.vAccel * params.fGravityScale.GetValueFromMod(m_BaseMods.GravityScale) + params.vAcceleration;

	pe_params_pos par_pos;
	par_pos.pos = m_Loc.t;
	par_pos.q = m_Loc.q;

	phys_geometry* pGeom = m_EmitGeom.m_pStatObj ? m_EmitGeom.m_pStatObj->GetPhysGeom() : NULL;

	m_Loc.s = params.fSize.GetMaxValue() * GetMain().GetParticleScale();

	if (params.ePhysicsType == ParticlePhysics_RigidBody)
	{
		if (!pGeom)
			return;

		// Make Physical Rigid Body.
		m_EmitGeom.m_pPhysEnt = GetPhysicalWorld()->CreatePhysicalEntity(PE_RIGID,&par_pos,m_EmitGeom.m_pStatObj,PHYS_FOREIGN_ID_RIGID_PARTICLE );
		 
		pe_geomparams partpos;
		partpos.density = params.fDensity;
		partpos.scale = m_Loc.s;
		partpos.flagsCollider = geom_colltype_debris;
		partpos.flags &= ~geom_colltype_debris; // don't collide with other particles.

		// Override surface index if specified.
		int idx =
			params.sSurfaceType.nIndex ? params.sSurfaceType.nIndex :
			params.pMaterial ? params.pMaterial->GetSurfaceTypeId() :
			0;
		if (idx)
		{
			partpos.pMatMapping = &idx;
			partpos.nMats = 1;
			partpos.surface_idx = 0;
		}
		m_EmitGeom.m_pPhysEnt->AddGeometry(pGeom, &partpos, 0);
		
		pe_simulation_params symparams;
		symparams.minEnergy = (0.2f)*(0.2f);
		symparams.damping = symparams.dampingFreefall = params.fAirResistance.GetValueFromMod(m_BaseMods.AirResistance);

		// Note: Customized gravity currently doesn't work for rigid body.
		symparams.gravity = symparams.gravityFreefall = vGravity;
		//symparams.softness = symparams.softnessGroup = 0.003f;
		//symparams.softnessAngular = symparams.softnessAngularGroup = 0.01f;
		symparams.maxLoggedCollisions = params.nMaxCollisionEvents;
		m_EmitGeom.m_pPhysEnt->SetParams(&symparams);

		pe_action_set_velocity velparam;
		velparam.v = m_Vel.vLin;
		velparam.w = m_Vel.vRot;
		m_EmitGeom.m_pPhysEnt->Action(&velparam);
	}
	else if (params.ePhysicsType == ParticlePhysics_SimplePhysics)
	{
		// Make Physical Particle.
		m_EmitGeom.m_pPhysEnt = GetPhysicalWorld()->CreatePhysicalEntity(PE_PARTICLE,&par_pos);
		pe_params_particle part;

		// Compute particle mass from volume of object.
		part.size = m_Loc.s;

		part.mass = params.fDensity * part.size * part.size * part.size;
		if (m_EmitGeom.m_pStatObj)
		{
			part.size *= m_EmitGeom.m_pStatObj->GetRadius() + 0.05f;
			if (pGeom)
				part.mass *= pGeom->V;
			else
				part.mass *= m_EmitGeom.m_pStatObj->GetAABB().GetVolume() * fSPHERE_VOLUME / 8.f;
		}
		else
		{
			// Assume spherical volume.
			part.mass *= fSPHERE_VOLUME;
		}

		part.thickness = params.fThickness * part.size;
		part.velocity = m_Vel.vLin.GetLength();
		if (part.velocity > 0.f)
			part.heading = m_Vel.vLin / part.velocity;
		part.q0 = m_Loc.q;
		part.wspin = m_Vel.vRot;
		part.q0 = m_Loc.q;
		
		if (m_EmitGeom.m_pStatObj)
		{
			Vec3 vSize = m_EmitGeom.m_pStatObj->GetBoxMax() - m_EmitGeom.m_pStatObj->GetBoxMin();
			char cMinAxis = GetMinAxis(vSize);
			part.normal = Vec3((cMinAxis=='x')?1.f:0,(cMinAxis=='y')?1.f:0,(cMinAxis=='z')?1.f:0);
		}

		part.surface_idx = GetSurfaceIndex();
		part.flags = /*particle_no_roll|*/particle_no_path_alignment;
		part.kAirResistance = params.fAirResistance.GetValueFromMod(m_BaseMods.AirResistance);
		part.gravity = vGravity;

		m_EmitGeom.m_pPhysEnt->SetParams(&part);
	}

	// Common settings.
	if (m_EmitGeom.m_pPhysEnt)
	{
		pe_params_flags pf;
		pf.flagsOR = pef_never_affect_triggers;
		pf.flagsOR |= pef_log_collisions;
		m_EmitGeom.m_pPhysEnt->SetParams(&pf);
		m_EmitGeom.m_pPhysEnt->AddRef();
	}


#endif
}

void CParticle::GetPhysicsState()
{
	if (m_EmitGeom.m_pPhysEnt)
	{
		pe_status_pos status_pos;
		if (m_EmitGeom.m_pPhysEnt->GetStatus(&status_pos))
		{
			m_Loc.t = status_pos.pos;
			m_Loc.q = status_pos.q;
		}
		pe_status_dynamics status_dyn;
		if (m_EmitGeom.m_pPhysEnt->GetStatus(&status_dyn))
		{
			m_Vel.vLin = status_dyn.v;
			m_Vel.vRot = status_dyn.w;
		}
	}
}

size_t CParticle::GetAllocationSize(const CParticleContainer* pCont)
{
	return pCont->GetHistorySteps() * sizeof(SParticleHistory)
		+ (pCont->NeedsCollisionInfo() ? sizeof(SCollisionInfo) : 0);
}

#ifdef _DEBUG

// Test for distribution evenness.
struct CChaosTest
{
	CChaosTest()
	{
		CChaosKey keyBase(0U);
		float f[100];
		for (uint32 i = 0; i < 100; i++)
		{
			f[i] = keyBase.Jumble(CChaosKey(i)) * 1.f;
		}
	}
};

static CChaosTest ChaosTest;

#endif
